package org.litespring.beans.factory.support;

import org.litespring.beans.BeanDefinition;
import org.litespring.beans.PropertyValue;
import org.litespring.beans.SimpleTypeConverter;
import org.litespring.beans.TypeConverter;
import org.litespring.beans.factory.BeanCreationException;
import org.litespring.beans.factory.config.ConfigurableBeanFactory;
import org.litespring.util.ClassUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory, BeanDefinitionRegistry {


    //存储从资源文件读取的bean定义
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private ClassLoader beanClassLoader;

    public DefaultBeanFactory() {
    }

    public Object getBean(String beanID) {
        BeanDefinition beanDefinition = this.getBeanDefinition(beanID);
        if (beanDefinition == null) {
            throw new BeanCreationException("beanDefinition not exist");
        }
        if (beanDefinition.isSingleton()) {
            Object bean = getSingletonBean(beanID);
            if (bean == null) {
                bean = createBean(beanDefinition);
                this.registrySingletonBean(beanID, bean);
            }
            return bean;
        }
        return createBean(beanDefinition);
    }

    private Object createBean(BeanDefinition beanDefinition) {
        //创建bean实例
        Object bean = initialBean(beanDefinition);
        //设置bean属性
        populateBean(beanDefinition, bean);
        return bean;
    }

    private void populateBean(BeanDefinition beanDefinition, Object bean) {
        List<PropertyValue> pvs = beanDefinition.getPropertyValues();
        if (pvs == null || pvs.isEmpty()) {
            return;
        }
        BeanDefinitionValueResolver resolver = new BeanDefinitionValueResolver(this);
        TypeConverter converter = new SimpleTypeConverter();
        try {
            for (PropertyValue propertyValue : pvs) {
                String propertyName = propertyValue.getName();
                Object originValue = propertyValue.getValue();
                Object resolvedValue = resolver.resolveValueIfNecessary(originValue);
                BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
                PropertyDescriptor[] propertyDescriptors =  beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor pd : propertyDescriptors) {
                    if (pd.getName().equals(propertyName)) {
                        Object convertValue = converter.convertIfNecessary(resolvedValue, pd.getPropertyType());
                        pd.getWriteMethod().invoke(bean,convertValue);
                        break;
                    }
                }
            }

        } catch (Exception e) {
            throw new BeanCreationException(beanDefinition.getBeanClassName()+" create Failed");
        }
    }

    private Object initialBean(BeanDefinition beanDefinition) {
        ClassLoader classLoader = this.getBeanClassLoader();
        Class<?> cls = null;
        String className = beanDefinition.getBeanClassName();
        try {
            cls = classLoader.loadClass(className);
            return cls.newInstance();
        } catch (Exception e) {
            throw new BeanCreationException("create bean for " + className + "failed");
        }
    }

    @Override
    public BeanDefinition getBeanDefinition(String ID) {
        return this.beanDefinitionMap.get(ID);
    }

    @Override
    public void registryBeanDefinition(String ID, BeanDefinition beanDefinition) {
        this.beanDefinitionMap.put(ID, beanDefinition);
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    @Override
    public ClassLoader getBeanClassLoader() {
        return this.beanClassLoader != null ? this.beanClassLoader : ClassUtils.getDefaultClassLoader();
    }
}
